package com.googlecode.afx.view;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.controlsfx.control.StatusBar;

import com.googlecode.afx.utils.ReflectiveAccessor;

import javafx.beans.property.Property;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.control.Accordion;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.PasswordField;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.RadioButton;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.SplitPane;
import javafx.scene.control.TableView;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.control.TitledPane;

/**
 * Node-wrapper to abstract the different node types of JavaFX and to provide a unique access to it.
 * 
 * @author Martin
 *
 */
public class NodeWrapper extends ReflectiveAccessor {

	public static final String PROP_TEXTPROPERTY = "textProperty";
	public static final String PROP_MESSAGEPROPERTY = "messageProperty";
	public static final String PROP_SELECTEDPROPERTY = "selectedProperty";
	public static final String PROP_PROGRESSPROPERTY = "progressProperty";
	public static final String PROP_GETITEMS = "getItems";
	
	private static Map<Class<? extends Node>, String> PRIMARY_VALUE_PROPERTY = null;
	
	static {
		
		PRIMARY_VALUE_PROPERTY = new HashMap<Class<? extends Node>, String>();
		PRIMARY_VALUE_PROPERTY.put(PasswordField.class, PROP_TEXTPROPERTY);
		PRIMARY_VALUE_PROPERTY.put(TextField.class, PROP_TEXTPROPERTY);
		PRIMARY_VALUE_PROPERTY.put(ListView.class, PROP_GETITEMS);
		PRIMARY_VALUE_PROPERTY.put(CheckBox.class, PROP_SELECTEDPROPERTY);
		PRIMARY_VALUE_PROPERTY.put(ComboBox.class, null);
		PRIMARY_VALUE_PROPERTY.put(TextArea.class, PROP_TEXTPROPERTY);
		PRIMARY_VALUE_PROPERTY.put(RadioButton.class, PROP_SELECTEDPROPERTY);
		PRIMARY_VALUE_PROPERTY.put(ProgressBar.class, PROP_PROGRESSPROPERTY);
		PRIMARY_VALUE_PROPERTY.put(StatusBar.class, PROP_PROGRESSPROPERTY);
		PRIMARY_VALUE_PROPERTY.put(Label.class, PROP_TEXTPROPERTY);
		PRIMARY_VALUE_PROPERTY.put(TableView.class, PROP_GETITEMS);
	}
	
	private String primaryValueProperty;
	
	private Node node;
	
	/**
	 * Constructor accepting a <code>javafx.scene.Node</code> instance for wrapping.
	 * @param node
	 */
	public NodeWrapper(Node node) {
		super(node);
		this.node = node;
		this.primaryValueProperty = PRIMARY_VALUE_PROPERTY.get(node.getClass());
	}

	public Object getPrimaryValue() {
		return this.getPrimaryObservableValue().getValue();
	}

	public void setPrimaryValue(Object value) {
		//TODO
	}
	
	public ObservableValue<?> getPrimaryObservableValue() {
		return this.getObservableValue(this.primaryValueProperty);
	}
	
	public Property<?> getPrimaryProperty() {
		return this.getProperty(this.primaryValueProperty);
	}

	public ObservableList<?> getPrimaryObservableList() {
		return this.getObservableList(this.primaryValueProperty);
	}
	
	public Object getValue(String propertyName) {
		return this.getObservableValue(propertyName).getValue();
	}
	
	public ObservableValue<?> getObservableValue(String propertyName) {
		return this.getPropertyValue(propertyName, ObservableValue.class);
	}
	
	public Property<?> getProperty(String propertyName) {
		return this.getPropertyValue(propertyName, Property.class);
	}
	
	public ObservableList<?> getObservableList(String propertyName) {
		return this.getPropertyValue(propertyName, ObservableList.class);
	}
	
	/**
	 * Checks, whether the property defined by <tt>propertyName</tt> supports the given <tt>type</tt>, i.e. it will be checked whether
	 * the property under the path is of that type.
	 * 
	 * @param propertyName
	 * @param type
	 * @return
	 */
	public boolean isPropertyOfType(String propertyName, Class<?> type) {
		return checkMethodReturnType(propertyName, this.getWrappedInstance().getClass(), type);
	}

	/**
	 * Checks, whether the default property supports the given <tt>type</tt>, i.e. it will be checked whether
	 * the property under the path is of that type.
	 * 
	 * @param type
	 * @return
	 */
	public boolean isPrimaryPropertyOfType(Class<?> type) {
		if(this.primaryValueProperty == null) {
			return false;
		}
		return checkMethodReturnType(this.primaryValueProperty, this.getWrappedInstance().getClass(), type);
	}
	

	/**
	 * Returns the name of the primary value property.
	 * 
	 * @return
	 */
	public String getPrimaryValueProperty() {
		return primaryValueProperty;
	}

	/**
	 * Traverses the JavaFX scene graph represented by this <code>NodeWrapper</code>. 
	 * Nodes are processed by applying the given <code>nodeHandler</code>.
	 * 
	 * @param parent
	 * @param nodeHandler
	 */
	
	public void traverse(NodeHandler nodeHandler) {
		traverse(this.node, nodeHandler);
	}
	
	/**
	 * Traverses the JavaFX scene graph represented by the node <code>parent</code>. 
	 * Nodes are processed by applying the given <code>nodeHandler</code>.
	 * 
	 * @param parent
	 * @param nodeHandler
	 */
	public static void traverse(Node parent, NodeHandler nodeHandler) {
		
		// process node... 
		nodeHandler.process(parent);

		// continue processing?
		if(!nodeHandler.continueTraversing()) {
			return;
		}
		
		// get child nodes and traverse on each node
		List<Node> childNodes = getChildren(parent);
		for(Node childNode : childNodes) {
			if(nodeHandler.continueTraversing()) {
				traverse(childNode, nodeHandler);
			} else {
				break;
			}
		}
	}	

	/**
	 * Return the children of this node.
	 * 
	 * @return
	 */
	public List<Node> getChildren() {
		return getChildren(this.node);
	}
	
	/**
	 * Return the children of the given node <tt>parent</tt>.
	 * 
	 * @return
	 */
	public static List<Node> getChildren(Node parent) {
		List<Node> childNodes = new ArrayList<Node>();
		
		if(parent instanceof Parent) {
			
			// certain components have different ways to get the child nodes
			if (parent instanceof TitledPane) {
				TitledPane titledPane = (TitledPane) parent;
				childNodes.add(titledPane.getContent());
			} else if (parent instanceof ScrollPane) {
				ScrollPane scrollPane = (ScrollPane) parent;
				childNodes.add(scrollPane.getContent());
			} else if (parent instanceof SplitPane) {
				SplitPane splitPane = (SplitPane) parent;
				childNodes.addAll(splitPane.getItems());
			} else if (parent instanceof Accordion) {
				Accordion accordion = (Accordion) parent;
				childNodes.addAll(accordion.getPanes());
			} else {
				Parent parentNode = (Parent) parent;
				childNodes.addAll(parentNode.getChildrenUnmodifiable());
			}
		}
		return childNodes;
	}

	/**
	 * Checks, if the given <tt>Node</tt> class has a primary value, i.e. 
	 * a central main value, like "textProperty" for <tt>Label</tt>, etc..
	 * 
	 * @param clazz
	 * @return
	 */
	public static boolean supportsPrimaryValue(Class<? extends Node> clazz) {
		return PRIMARY_VALUE_PROPERTY.containsKey(clazz);
	}
	
	
	public Node getNode() {
		return node;
	}

}


